package la.renzhen.rtpt.config.provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import la.renzhen.rtpt.config.FluentConfiguration;
import la.renzhen.rtpt.config.ConfigurationProvider;
import la.renzhen.rtpt.config.ConfigurationSource;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Map;


/**
 * <p>
 *
 * @author <a href="mailto:zhouhaichao@2008.sina.com">haiker</a>
 * @version 30/05/2018 3:04 PM
 */
@Data
@Accessors(fluent = true)
public class ComposeConfigurationProvider implements ConfigurationProvider {

    ConfigurationSource configurationSource;
    String environment;
    String prefix;

    ObjectMapper mapper = new ObjectMapper();

    public ComposeConfigurationProvider(ConfigurationSource configurationSource, String environment, String prefix) {
        this.configurationSource = configurationSource;
        this.environment = environment;
        this.prefix = prefix;
    }

    @Override
    public Map<String, String> allConfiguration() {
        return configurationSource.loadAll(environment, prefix);
    }

    @Override
    public String getProperty(String key) {
        return configurationSource.get(key, environment);
    }

    @Override
    @SneakyThrows
    public <T> T getProperty(String key, Type type, String defaultValue) {
        String value = configurationSource.get(environment, key);
        if (value == null) {
            value = defaultValue;
        }
        if (type.equals(String.class)) {
            return (T) value;
        }
        return this.mapper.readValue(value, this.mapper.constructType(type));
    }

    @Override
    public <T> T invoke(Class<T> configurationClass) {
        FluentConfiguration configuration = configurationClass.getAnnotation(FluentConfiguration.class);
        String prefix = this.prefix;
        if (configuration != null && !"".equals(configuration.value())) {
            prefix = hasText(prefix) ? prefix + "." + configuration.value() : configuration.value();
        }
        return (T) Proxy.newProxyInstance(configurationClass.getClassLoader(), new Class<?>[]{configurationClass}, new BindInvocationHandler(this, prefix));
    }


    class BindInvocationHandler implements InvocationHandler {
        private final String prefix;
        private final ConfigurationProvider provider;

        private BindInvocationHandler(ConfigurationProvider provider, String prefix) {
            this.provider = provider;
            this.prefix = prefix;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            FluentConfiguration.DefaultValue defaultValue = method.getAnnotation(FluentConfiguration.DefaultValue.class);
            String key = (hasText(prefix) ? prefix + "." : "") + method.getName();
            String defValue = defaultValue != null ? defaultValue.value() : null;
            return provider.getProperty(key, method.getGenericReturnType(), defValue);
        }
    }

    private static boolean hasText(String text) {
        return text != null && !"".equals(text.trim());
    }
}
